package dou.net.socket;

import org.apache.log4j.LogManager;
import org.apache.log4j.Logger;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Set;
import java.util.concurrent.LinkedBlockingDeque;

/**
 * 实现了服务端非阻塞套接字对象的功能
 * @author wizardc
 */
public abstract class AbstractServerSocket implements IServerSocket
{
    private static Logger logger = LogManager.getLogger("GameServer");

    // 记录该套接字服务对象的名称
    private String _serverName;

    // 记录套接字数据流读取线程数量
    private int _readThreadCount;
    // 记录套接字数据流写入线程数量
    private int _writeThreadCount;

    // 记录套接字服务端的地址
    private String _host;
    // 记录套接字服务端的接口
    private int _port;

    // 记录所有套接字通道的 Selector 对象
    private Selector _selector;
    // 记录套接字服务通道对象
    private ServerSocketChannel _serverSocketChannel;
    // 记录套接字服务对象
    private ServerSocket _serverSocket;

    // 记录读取线程可阻塞双向队列
    private LinkedBlockingDeque<IClientSocket> _readQueue;
    // 记录写入线程可阻塞双向队列
    private LinkedBlockingDeque<IClientSocket> _writeQueue;

    // 记录每一个客户端套接字通道的哈希映射对象
    private HashMap<SocketChannel, AbstractClientSocket> _socketMap;

    /**
     * 构造函数
     * @param serverName 该套接字服务对象的名称
     * @param readThreadCount 确定读取队列的线程个数
     * @param writeThreadCount 确定写入队列的线程个数
     */
    public AbstractServerSocket(String serverName, int readThreadCount, int writeThreadCount)
    {
        _serverName = serverName;
        _readThreadCount = readThreadCount;
        _writeThreadCount = writeThreadCount;
        _readQueue = new LinkedBlockingDeque<IClientSocket>();
        _writeQueue = new LinkedBlockingDeque<IClientSocket>();
        _socketMap = new HashMap<SocketChannel, AbstractClientSocket>();
    }

    @Override
    public void startService(String host, int port) throws IOException
    {
        _host = host;
        _port = port;
        // 打开一个 Selector 对象
        _selector = Selector.open();
        // 打开一个服务套接字通道对象
        _serverSocketChannel = ServerSocketChannel.open();
        // 设置该服务套接字对象为非阻塞模式
        _serverSocketChannel.configureBlocking(false);
        // 获取该通道的套接字服务对象
        _serverSocket = _serverSocketChannel.socket();
        // 将地址绑定到该服务套接字对象上
        if(host == null)
        {
            _serverSocket.bind(new InetSocketAddress(_port));
        }
        else
        {
            _serverSocket.bind(new InetSocketAddress(_host, _port));
        }
        // 将服务套接字通道对象注册到 Selector 对象，并侦听其连接事件
        _serverSocketChannel.register(_selector, SelectionKey.OP_ACCEPT);
        // 开启套接字读写线程
        startReadThread();
        startWriteThread();
        // 开始循环
        while(true)
        {
            // 该方法为阻塞方法，有选择时才会继续运行
            _selector.select();
            // 获取需要处理的对象
            Set<SelectionKey> selectedKeys = _selector.selectedKeys();
            // 获取迭代处理对象
            Iterator<SelectionKey> iterator = selectedKeys.iterator();
            // 如果有需要处理的对象
            while(iterator.hasNext())
            {
                // 取出该对象
                SelectionKey key = iterator.next();
                // 在源列表对象中移除最后取出的对象
                iterator.remove();
                // 如果该键无效则跳过这次的处理
                if(!key.isValid())
                {
                    continue;
                }
                // 捕获所有异常
                try
                {
                    // 获取此键的 ready 操作集合
                    int readyOps = key.readyOps();
                    // 有套接字连接请求
                    if((readyOps & SelectionKey.OP_ACCEPT) != 0)
                    {
                        // 获取该键的服务套接字通道
                        ServerSocketChannel channel = (ServerSocketChannel) key.channel();
                        // 获取该键的套接字通道
                        SocketChannel socketChannel = channel.accept();
                        // 客户端 IP 检测
                        if(!checkClientIP(socketChannel))
                        {
                            break;
                        }
                        // 记录客户端
                        AbstractClientSocket clientSocket = createClientSocket(this, _selector, socketChannel);
                        _socketMap.put(socketChannel, clientSocket);
                        logger.info("client connect: " + clientSocket.ipAddressStr());
                    }
                    else
                    {
                        // 套接字读取请求
                        if((readyOps & SelectionKey.OP_READ) != 0)
                        {
                            // 去掉 SelectionKey.OP_READ 标志位，去掉了该标志位以后该客户端套接字有数据可以读取时都会被忽略，直到处理完这次的数据后再添加该标志位
                            key.interestOps((key.interestOps() & (~SelectionKey.OP_READ)));
                            // 如果该套接字通道存在并且没有被添加到读取队列中则添加到读取队列中
                            if(_socketMap.get(key.channel()) != null && !_readQueue.contains(_socketMap.get(key.channel())))
                            {
                                _readQueue.add(_socketMap.get(key.channel()));
                            }
                        }
                        // 套接字写入请求
                        if((readyOps & SelectionKey.OP_WRITE) != 0)
                        {
                            // 去掉 SelectionKey.OP_WRITE 标志位，去掉了该标志位以后该客户端套接字有数据需要发送时都会被忽略，直到发送完这次的数据后再添加该标志位
                            key.interestOps((key.interestOps() & (~SelectionKey.OP_WRITE)));
                            // 如果该套接字通道存在并且没有被添加到写入队列中则添加到写入队列中
                            if(_socketMap.get(key.channel()) != null && !_writeQueue.contains(_socketMap.get(key.channel())))
                            {
                                _writeQueue.add(_socketMap.get(key.channel()));
                            }
                        }
                    }
                }
                catch(Throwable throwable)
                {
                    try
                    {
                        if(_socketMap.containsKey(key.channel()))
                        {
                            _socketMap.get(key.channel()).close();
                            _socketMap.remove(key.channel());
                        }
                        else if(key.channel() != null)
                        {
                            ((SocketChannel) key.channel()).close();
                        }
                    }
                    catch(Exception exception)
                    {
                    }
                }
            }
        }
    }

    /**
     * 开启并创建对应的套接字读取线程
     */
    private void startReadThread()
    {
        for(int i = 0; i < _readThreadCount; i++)
        {
            new ReadThread(_serverName + "-" + i, _readQueue).start();
        }
    }

    /**
     * 开启并创建对应的套接字写入线程
     */
    private void startWriteThread()
    {
        for(int i = 0; i < _writeThreadCount; i++)
        {
            new WriteThread(_serverName + "-" + i, _writeQueue).start();
        }
    }

    @Override
    public void send(IClientSocket socket)
    {
        _writeQueue.add(socket);
    }

    @Override
    public synchronized void remove(SocketChannel socket)
    {
        _socketMap.remove(socket);
    }

    /**
     * 检测连接的客户端 IP，如果不记录该客户端则该客户端会连接到服务端，但对于该客户端的所有请求都不进行处理
     * @param socketChannel 请求连接的客户端通道
     * @return 返回 true 则记录该客户端，返回 false 则不记录该客户端
     */
    protected abstract boolean checkClientIP(SocketChannel socketChannel);

    /**
     * 创建新的套接字客户端对象
     * @param server 套接字服务对象
     * @param selector 套接字通道的 Selector 对象
     * @param socketChannel 该套接字客户端的套接字通道对象
     * @return 返回一个套接字对象
     * @throws IOException 创建套接字对象出现输入输出异常时抛出该异常
     */
    protected abstract AbstractClientSocket createClientSocket(IServerSocket server, Selector selector, SocketChannel socketChannel) throws IOException;
}
